//
//  SCGIFImageView.m
//  TestGIF
//
//  Created by shichangone on 11-7-12.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import "SCGIFImageView.h"

@implementation AnimatedGifFrame

@synthesize data, delay, disposalMethod, area, header;

- (void) dealloc
{
    [data release];
    [header release];
    [super dealloc];
}

@end

@implementation SCGIFImageView
@synthesize GIF_frames;

+ (BOOL)isGifImage:(NSData*)imageData {
    const char* buf = (const char*)[imageData bytes];
    if (buf[0] == 0x47 && buf[1] == 0x49 && buf[2] == 0x46 && buf[3] == 0x38) {
        return YES;
    }
    return NO;
}

+ (NSMutableArray*)getGifFrames:(NSData*)gifImageData {
    SCGIFImageView* gifImageView = [[SCGIFImageView alloc] initWithGIFData:gifImageData];
    if (!gifImageView) {
        return nil;
    }
    
    NSMutableArray* gifFrames = gifImageView.GIF_frames;
    [[gifFrames retain] autorelease];
    [gifImageView release];
    return gifFrames;
}

- (id)initWithGIFFile:(NSString*)gifFilePath {
    NSData *imageData = [NSData dataWithContentsOfFile:gifFilePath];
    return [self initWithGIFData:imageData];
}

- (id)initWithGIFData:(NSData*)gifImageData {
    if (gifImageData.length < 4) {
        return nil;
    }
    
    if (![SCGIFImageView isGifImage:gifImageData]) {
        UIImage* image = [UIImage imageWithData:gifImageData];
        return [super initWithImage:image];
    }
    
    [self decodeGIF:gifImageData];
    
    if (GIF_frames.count <= 0) {
        UIImage* image = [UIImage imageWithData:gifImageData];
        return [super initWithImage:image];
    }
    
    self = [super init];
    if (self) {
        [self loadImageData];
    }
    
    return self;
}

- (void)setGIF_frames:(NSMutableArray *)gifFrames {
    [gifFrames retain];
    
    if (GIF_frames) {
        [GIF_frames release];
    }
    GIF_frames = gifFrames;
    
    [self loadImageData];
}

- (void)loadImageData {
    // Add all subframes to the animation
    NSMutableArray *array = [[NSMutableArray alloc] init];
    for (NSUInteger i = 0; i < [GIF_frames count]; i++)
    {
        [array addObject: [self getFrameAsImageAtIndex:i]];
    }
    
    NSMutableArray *overlayArray = [[NSMutableArray alloc] init];
    UIImage *firstImage = [array objectAtIndex:0];
    CGSize size = firstImage.size;
    CGRect rect = CGRectZero;
    rect.size = size;
    
    UIGraphicsBeginImageContext(size);
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    int i = 0;
    AnimatedGifFrame *lastFrame = nil;
    for (UIImage *image in array)
    {
        // Get Frame
        AnimatedGifFrame *frame = [GIF_frames objectAtIndex:i];
        
        // Initialize Flag
        UIImage *previousCanvas = nil;
        
        // Save Context
        CGContextSaveGState(ctx);
        // Change CTM
        CGContextScaleCTM(ctx, 1.0, -1.0);
        CGContextTranslateCTM(ctx, 0.0, -size.height);
        
        // Check if lastFrame exists
        CGRect clipRect;
        
        // Disposal Method (Operations before draw frame)
        switch (frame.disposalMethod)
        {
            case 1: // Do not dispose (draw over context)
                // Create Rect (y inverted) to clipping
                clipRect = CGRectMake(frame.area.origin.x, size.height - frame.area.size.height - frame.area.origin.y, frame.area.size.width, frame.area.size.height);
                // Clip Context
                CGContextClipToRect(ctx, clipRect);
                break;
            case 2: // Restore to background the rect when the actual frame will go to be drawed
                // Create Rect (y inverted) to clipping
                clipRect = CGRectMake(frame.area.origin.x, size.height - frame.area.size.height - frame.area.origin.y, frame.area.size.width, frame.area.size.height);
                // Clip Context
                CGContextClipToRect(ctx, clipRect);
                break;
            case 3: // Restore to Previous
                // Get Canvas
                previousCanvas = UIGraphicsGetImageFromCurrentImageContext();
                
                // Create Rect (y inverted) to clipping
                clipRect = CGRectMake(frame.area.origin.x, size.height - frame.area.size.height - frame.area.origin.y, frame.area.size.width, frame.area.size.height);
                // Clip Context
                CGContextClipToRect(ctx, clipRect);
                break;
        }
        
        // Draw Actual Frame
        CGContextDrawImage(ctx, rect, image.CGImage);
        // Restore State
        CGContextRestoreGState(ctx);
        
        //delay must larger than 0, the minimum delay in firefox is 10.
        if (frame.delay <= 0) {
            frame.delay = 10;
        }
        [overlayArray addObject:UIGraphicsGetImageFromCurrentImageContext()];
        
        // Set Last Frame
        lastFrame = frame;
        
        // Disposal Method (Operations afte draw frame)
        switch (frame.disposalMethod)
        {
            case 2: // Restore to background color the zone of the actual frame
                // Save Context
                CGContextSaveGState(ctx);
                // Change CTM
                CGContextScaleCTM(ctx, 1.0, -1.0);
                CGContextTranslateCTM(ctx, 0.0, -size.height);
                // Clear Context
                CGContextClearRect(ctx, clipRect);
                // Restore Context
                CGContextRestoreGState(ctx);
                break;
            case 3: // Restore to Previous Canvas
                // Save Context
                CGContextSaveGState(ctx);
                // Change CTM
                CGContextScaleCTM(ctx, 1.0, -1.0);
                CGContextTranslateCTM(ctx, 0.0, -size.height);
                // Clear Context
                CGContextClearRect(ctx, lastFrame.area);
                // Draw previous frame
                CGContextDrawImage(ctx, rect, previousCanvas.CGImage);
                // Restore State
                CGContextRestoreGState(ctx);
                break;
        }
        
        // Increment counter
        i++;
    }
    UIGraphicsEndImageContext();
    
    [self setImage:[overlayArray objectAtIndex:0]];
    [self setAnimationImages:overlayArray];
    
    [overlayArray release];
    [array release];
    
    // Count up the total delay, since Cocoa doesn't do per frame delays.
    double total = 0;
    for (AnimatedGifFrame *frame in GIF_frames) {
        total += frame.delay;
    }
    
    // GIFs store the delays as 1/100th of a second,
    // UIImageViews want it in seconds.
    [self setAnimationDuration:total/100];
    
    // Repeat infinite
    [self setAnimationRepeatCount:0];
    
    [self startAnimating];
}

- (void)dealloc {
    if (GIF_buffer != nil)
    {
        [GIF_buffer release];
    }
    
    if (GIF_screen != nil)
    {
        [GIF_screen release];
    }
    
    if (GIF_global != nil)
    {
        [GIF_global release];
    }
    
    [GIF_frames release];
    
    [super dealloc];
}

- (void) decodeGIF:(NSData *)GIFData {
    GIF_pointer = GIFData;
    
    if (GIF_buffer != nil)
    {
        [GIF_buffer release];
    }
    
    if (GIF_global != nil)
    {
        [GIF_global release];
    }
    
    if (GIF_screen != nil)
    {
        [GIF_screen release];
    }
    
    [GIF_frames release];
    
    GIF_buffer = [[NSMutableData alloc] init];
    GIF_global = [[NSMutableData alloc] init];
    GIF_screen = [[NSMutableData alloc] init];
    GIF_frames = [[NSMutableArray alloc] init];
    
    // Reset file counters to 0
    dataPointer = 0;
    
    [self GIFSkipBytes: 6]; // GIF89a, throw away
    [self GIFGetBytes: 7]; // Logical Screen Descriptor
    
    // Deep copy
    [GIF_screen setData: GIF_buffer];
    
    // Copy the read bytes into a local buffer on the stack
    // For easy byte access in the following lines.
    int length = [GIF_buffer length];
    unsigned char aBuffer[length];
    [GIF_buffer getBytes:aBuffer length:length];
    
    if (aBuffer[4] & 0x80) GIF_colorF = 1; else GIF_colorF = 0;
    if (aBuffer[4] & 0x08) GIF_sorted = 1; else GIF_sorted = 0;
    GIF_colorC = (aBuffer[4] & 0x07);
    GIF_colorS = 2 << GIF_colorC;
    
    if (GIF_colorF == 1)
    {
        [self GIFGetBytes: (3 * GIF_colorS)];
        
        // Deep copy
        [GIF_global setData:GIF_buffer];
    }
    
    unsigned char bBuffer[1];
    while ([self GIFGetBytes:1] == YES)
    {
        [GIF_buffer getBytes:bBuffer length:1];
        
        if (bBuffer[0] == 0x3B)
        { // This is the end
            break;
        }
        
        switch (bBuffer[0])
        {
            case 0x21:
                // Graphic Control Extension (#n of n)
                [self GIFReadExtensions];
                break;
            case 0x2C:
                // Image Descriptor (#n of n)
                [self GIFReadDescriptor];
                break;
        }
    }
    
    // clean up stuff
    [GIF_buffer release];
    GIF_buffer = nil;
    
    [GIF_screen release];
    GIF_screen = nil;
    
    [GIF_global release];
    GIF_global = nil;
}

- (void) GIFReadExtensions {
    // 21! But we still could have an Application Extension,
    // so we want to check for the full signature.
    unsigned char cur[1], prev[1];
    [self GIFGetBytes:1];
    [GIF_buffer getBytes:cur length:1];
    
    while (cur[0] != 0x00)
    {
        
        // TODO: Known bug, the sequence F9 04 could occur in the Application Extension, we
        //       should check whether this combo follows directly after the 21.
        if (cur[0] == 0x04 && prev[0] == 0xF9)
        {
            [self GIFGetBytes:5];
            
            AnimatedGifFrame *frame = [[AnimatedGifFrame alloc] init];
            
            unsigned char buffer[5];
            [GIF_buffer getBytes:buffer length:5];
            frame.disposalMethod = (buffer[0] & 0x1c) >> 2;
            //NSLog(@"flags=%x, dm=%x", (int)(buffer[0]), frame.disposalMethod);
            
            // We save the delays for easy access.
            frame.delay = (buffer[1] | buffer[2] << 8);
            
            unsigned char board[8];
            board[0] = 0x21;
            board[1] = 0xF9;
            board[2] = 0x04;
            
            for(int i = 3, a = 0; a < 5; i++, a++)
            {
                board[i] = buffer[a];
            }
            
            frame.header = [NSData dataWithBytes:board length:8];
            
            [GIF_frames addObject:frame];
            [frame release];
            break;
        }
        
        prev[0] = cur[0];
        [self GIFGetBytes:1];
        [GIF_buffer getBytes:cur length:1];
    }
}

- (void) GIFReadDescriptor {
    [self GIFGetBytes:9];
    
    // Deep copy
    NSMutableData *GIF_screenTmp = [NSMutableData dataWithData:GIF_buffer];
    
    unsigned char aBuffer[9];
    [GIF_buffer getBytes:aBuffer length:9];
    
    CGRect rect;
    rect.origin.x = ((int)aBuffer[1] << 8) | aBuffer[0];
    rect.origin.y = ((int)aBuffer[3] << 8) | aBuffer[2];
    rect.size.width = ((int)aBuffer[5] << 8) | aBuffer[4];
    rect.size.height = ((int)aBuffer[7] << 8) | aBuffer[6];
    
    AnimatedGifFrame *frame = [GIF_frames lastObject];
    frame.area = rect;
    
    if (aBuffer[8] & 0x80) GIF_colorF = 1; else GIF_colorF = 0;
    
    unsigned char GIF_code = GIF_colorC, GIF_sort = GIF_sorted;
    
    if (GIF_colorF == 1)
    {
        GIF_code = (aBuffer[8] & 0x07);
        
        if (aBuffer[8] & 0x20)
        {
            GIF_sort = 1;
        }
        else
        {
            GIF_sort = 0;
        }
    }
    
    int GIF_size = (2 << GIF_code);
    
    size_t blength = [GIF_screen length];
    unsigned char bBuffer[blength];
    [GIF_screen getBytes:bBuffer length:blength];
    
    bBuffer[4] = (bBuffer[4] & 0x70);
    bBuffer[4] = (bBuffer[4] | 0x80);
    bBuffer[4] = (bBuffer[4] | GIF_code);
    
    if (GIF_sort)
    {
        bBuffer[4] |= 0x08;
    }
    
    NSMutableData *GIF_string = [NSMutableData dataWithData:[[NSString stringWithString:@"GIF89a"] dataUsingEncoding: NSUTF8StringEncoding]];
    [GIF_screen setData:[NSData dataWithBytes:bBuffer length:blength]];
    [GIF_string appendData: GIF_screen];
    
    if (GIF_colorF == 1)
    {
        [self GIFGetBytes:(3 * GIF_size)];
        [GIF_string appendData:GIF_buffer];
    }
    else
    {
        [GIF_string appendData:GIF_global];
    }
    
    // Add Graphic Control Extension Frame (for transparancy)
    [GIF_string appendData:frame.header];
    
    char endC = 0x2c;
    [GIF_string appendBytes:&endC length:sizeof(endC)];
    
    size_t clength = [GIF_screenTmp length];
    unsigned char cBuffer[clength];
    [GIF_screenTmp getBytes:cBuffer length:clength];
    
    cBuffer[8] &= 0x40;
    
    [GIF_screenTmp setData:[NSData dataWithBytes:cBuffer length:clength]];
    
    [GIF_string appendData: GIF_screenTmp];
    [self GIFGetBytes:1];
    [GIF_string appendData: GIF_buffer];
    
    while (true)
    {
        [self GIFGetBytes:1];
        [GIF_string appendData: GIF_buffer];
        
        unsigned char dBuffer[1];
        [GIF_buffer getBytes:dBuffer length:1];
        
        long u = (long) dBuffer[0];
        
        if (u != 0x00)
        {
            [self GIFGetBytes:u];
            [GIF_string appendData: GIF_buffer];
        }
        else
        {
            break;
        }
        
    }
    
    endC = 0x3b;
    [GIF_string appendBytes:&endC length:sizeof(endC)];
    
    // save the frame into the array of frames
    frame.data = GIF_string;
}

- (bool) GIFGetBytes:(int)length {
    if (GIF_buffer != nil)
    {
        [GIF_buffer release]; // Release old buffer
        GIF_buffer = nil;
    }
    
    if ((NSInteger)[GIF_pointer length] >= dataPointer + length) // Don't read across the edge of the file..
    {
        GIF_buffer = [[GIF_pointer subdataWithRange:NSMakeRange(dataPointer, length)] retain];
        dataPointer += length;
        return YES;
    }
    else
    {
        return NO;
    }
}

- (bool) GIFSkipBytes: (int) length {
    if ((NSInteger)[GIF_pointer length] >= dataPointer + length)
    {
        dataPointer += length;
        return YES;
    }
    else
    {
        return NO;
    }
}

- (NSData*) getFrameAsDataAtIndex:(int)index {
    if (index < (NSInteger)[GIF_frames count])
    {
        return ((AnimatedGifFrame *)[GIF_frames objectAtIndex:index]).data;
    }
    else
    {
        return nil;
    }
}

- (UIImage*) getFrameAsImageAtIndex:(int)index {
    NSData *frameData = [self getFrameAsDataAtIndex: index];
    UIImage *image = nil;
    
    if (frameData != nil)
    {
        image = [UIImage imageWithData:frameData];
    }
    
    return image;
}


@end


